package com.anjie.powerproject.service.impl;

import com.anjie.powerproject.common.Constants;
import com.anjie.powerproject.common.ModelTypeEnum;
import com.anjie.powerproject.config.DataCache;
import com.anjie.powerproject.config.DataObject;
import com.anjie.powerproject.config.DataSet;
import com.anjie.powerproject.config.ExecuteResult;
import com.anjie.powerproject.entity.DataDay;
import com.anjie.powerproject.entity.Model;
import com.anjie.powerproject.entity.Point;
import com.anjie.powerproject.repository.DeviceRepository;
import com.anjie.powerproject.repository.ModelRepository;
import com.anjie.powerproject.repository.PointRepository;
import com.anjie.powerproject.service.DataDayService;
import com.anjie.powerproject.service.ModelService;
import com.anjie.powerproject.service.PreDataDayService;
import com.anjie.powerproject.strategy.utils.DateUtils;
import com.anjie.powerproject.strategy.utils.DecimalFormatUtils;
import com.anjie.powerproject.util.CsvData;
import com.anjie.powerproject.util.CsvUtil;
import com.anjie.powerproject.util.PythonExcutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Service
public class ModelServiceImpl implements ModelService {
    private static final Logger logger = LoggerFactory.getLogger(ModelServiceImpl.class);

    @Autowired
    ModelRepository modelRepository;

    @Autowired
    DataDayService dataDayService;

    @Autowired
    PreDataDayService preDataDayService;

    @Autowired
    DataCache dataCache;

    @Autowired
    PointRepository pointRepository;

    @Override
    public List<Model> getAllModel(String deviceCode, ModelTypeEnum modelType) {
        return modelRepository.getModelByDeviceCodeAndType(deviceCode, modelType.getCode());
    }

    public List<Model> getAllModel() {
        return modelRepository.findAll();
    }

    @Override
    public boolean deleteModelById(Integer id){
        Model Model = modelRepository.findById(id).orElse(null);
        if(Model != null){
            modelRepository.deleteById(id);
            return true;
        }
        return false;
    }
    @Override
    public int updateModel(Integer id,String modelName,String deviceCode,Integer pointId, String modelVersion){
//        System.out.println("id "+id);
//        System.out.println("modelName: "+modelName);
//        System.out.println("deviceCode: "+deviceCode);
//        System.out.println("modelVersion: "+modelVersion);
//        System.out.println("pointId: "+pointId);
//        Integer pointId=pointRepository.getPointByDevCode(deviceCode).getId();

        return modelRepository.updateModel(id,modelName,deviceCode,pointId,modelVersion);
    }

    @Override
    public Model getSelectedModel(String deviceCode, ModelTypeEnum modelType) {
        Model model = modelRepository.getSelectedModel(deviceCode, modelType.getCode());
        if (model == null) {
            throw new RuntimeException(String.format("%s模型未设置当前使用的模型", modelType.getType()));
        }
        return model;
    }

    @Override
    public Model getModelById(Integer modelId) {
        return modelRepository.getOne(modelId);
    }

    @Override
    public void saveAllModel(List<Model> models) {
        modelRepository.saveAll(models);
        modelRepository.flush();
    }
    @Override
    public Integer getModelIdByName(String modelName){
        return modelRepository.getModelIdByName(modelName);
    }

    @Override
    public void save(Model model) {
        modelRepository.saveAndFlush(model);
    }

    public int getCountModelByName(String modelName){
        return modelRepository.getCountModelByName(modelName);
    }

    @Override
    public void train(String basePath, DataSet dataSet, Integer modelId, String dataPath) {
        if (basePath.lastIndexOf("/") != basePath.length() - 1) {
            basePath += "/";
        }

        // 将预处理后的数据存储为csv文件，保存到磁盘中
        CsvData csvData = CsvUtil.buildCsvData(dataSet.getColumns(), dataSet.getData());
        try {
//            System.out.println("train:"+basePath+"-------"+"dataPath:"+dataPath);
            CsvUtil.dump(basePath + Constants.PYTHON_DIR + dataPath, csvData);
        } catch (Exception e) {
            throw new RuntimeException("存储训练集失败: " + e.getMessage());
        }

        PythonExcutor.runCreateScript(basePath + Constants.PYTHON_DIR + "run_manual.py", modelId.toString(), "train", basePath + Constants.PYTHON_DIR + dataPath);
    }

    @Override
    public void predict(String basePath, DataSet dataSet, Integer modelId, String dataPath) throws ParseException {
        if (basePath.lastIndexOf("/") != basePath.length() - 1) {
            basePath += "/";
        }

        // 将预处理后的数据存储为csv文件，保存到磁盘中
        CsvData csvData = CsvUtil.buildCsvData(dataSet.getColumns(), dataSet.getData());
        String error = null;
        // 执行完成之后，保存执行结果到内存中，将cache中的执行状态设置为执行完成
        ExecuteResult executeResult = dataCache.loadExecuteResult(modelId);

        try {
            CsvUtil.dump(basePath + Constants.PYTHON_DIR + dataPath, csvData);
        } catch (Exception e) {
            logger.error("存储测试集失败: {}", e.getMessage());
            error = String.format("存储测试集失败: %s", e.getMessage());
        }

        if (error == null) {
            error = PythonExcutor.runPredictScript(basePath + Constants.PYTHON_DIR + "run_manual.py", executeResult, modelId.toString(), "test", basePath + Constants.PYTHON_DIR + dataPath);
//            error = PythonExcutor.runPredictScript(basePath + Constants.PYTHON_DIR + "test.py", executeResult);
        }

        if (error != null) {
            executeResult.setError(error);
        } else {
            // 1. 读取dataSet中的第一列数据，因为默认第一列是日期
            List<String> dateList = dataSet.getData().stream().map(row -> row.get(0)).sorted().collect(Collectors.toList());
            executeResult.setDateList(dateList);
            // 2. 使用dateList + modelId去查询预测结果
            Date startTime = DateUtils.parseStringToDate(DateUtils.ymdFormat, dateList.get(0));
            Date endTime = DateUtils.parseStringToDate(DateUtils.ymdFormat, dateList.get(dateList.size() - 1));
            List<BigDecimal> modelList = preDataDayService.getPreDataDayValueByCodeAndDateRange(dateList, executeResult.getDeviceCode(), modelId, startTime, endTime);
            executeResult.setModelList(modelList);

            Model model = getSelectedModel(executeResult.getDeviceCode(), executeResult.getModelType());
            executeResult.setSelectedModel(model.getModelName());
            List<BigDecimal> selectedList = preDataDayService.getPreDataDayValueByCodeAndDateRange(dateList, executeResult.getDeviceCode(), model.getId(), startTime, endTime);
            executeResult.setSelectedList(selectedList);

            List<BigDecimal> historyData = dataDayService.getDataDayValues(dateList, executeResult.getDeviceCode(), startTime,endTime);
            executeResult.setHistoryList(historyData);

            // 计算误差值
            BigDecimal[] analyseValues = new BigDecimal[2];
            analyseValues[0] = computeAnalyseValue(modelList, historyData);
            analyseValues[1] = computeAnalyseValue(selectedList, historyData);
            executeResult.setAnalyseValues(analyseValues);
        }

        executeResult.setComplete(true);
        dataCache.saveExecuteResult(modelId, executeResult);
    }

    private BigDecimal computeAnalyseValue(List<BigDecimal> source, List<BigDecimal> target) {
        assert source.size() == target.size();

//        double sum = 0.0;
//        for (int i = 0; i < target.size(); i++) {
//            sum += target.get(i);
//        }
//        double mean = sum / target.size();
//
//        // R方
//        double scoreTop = 0.0;
//        double scoreBottom = 0.0;
//        for (int i = 0; i < source.size(); i++) {
//            scoreTop += (source.get(i) - target.get(i)) * (source.get(i) - target.get(i));
//            scoreBottom += (target.get(i) - mean) * (target.get(i) - mean);
//        }
//
//        double v = 1 - scoreTop / scoreBottom;
//
//        if (Double.isFinite(v)) {
//            return DecimalFormatUtils.round(1 - scoreTop / scoreBottom, "0.00");
//        }
//
//        return v;

        //平均绝对百分比误差
        BigDecimal num = BigDecimal.valueOf(0.0);
        BigDecimal mae = BigDecimal.valueOf(0.0);
        int a = 0;        //记录有多少天实际值为0的无效数据
//        for (int i = 0; i < source.size(); i++) {
//            if (target.get(i) == 0) {
//                a = a + 1;
//                continue;
//            }
//            if(Math.abs((source.get(i) / target.get(i) - 1))>1)
//                num++;
//            else
//                num += Math.abs((source.get(i) / target.get(i) - 1));
//        }
//
//        if (target.size()==a)   //每天的实际值都为0的情况
//            return DecimalFormatUtils.round(0.0, "0.00");
//
//        mae = num/(target.size()-a);
//
//        return DecimalFormatUtils.round(1-mae, "0.00");//误差越大，准确率越低
        for (int i = 0; i < source.size(); i++) {
            if (target.get(i).compareTo(BigDecimal.ZERO) == 0){
                a = a + 1;
                continue;
            }
            BigDecimal temp = source.get(i).divide(target.get(i),2, RoundingMode.HALF_UP).subtract(BigDecimal.ONE);
            if (temp.abs().compareTo(BigDecimal.ONE) > 0)
                num = num.add(BigDecimal.ONE);
            else
                num = num.add(temp);
        }

        if (target.size()==a)   //每天的实际值都为0的情况
            return BigDecimal.ZERO;

        mae = num.divide(BigDecimal.valueOf(target.size()-a),2,RoundingMode.HALF_UP);

        return BigDecimal.ONE.subtract(mae);//误差越大，准确率越低
    }

}
